#include <cassert>

#include "Chunk2.h"
#include "Config.h"

namespace MemMgr
{

void Chunk2::Init(const size_t iBlockSize, const BlockCounter iNumBlocks)
{
	assert(iBlockSize > 0); 
	assert(iNumBlocks > 0);
	assert(iNumBlocks <= MAX_NUM_BLOCKS);

	const size_t minBlockSize = sizeof(BlockCounter);
	size_t blockSize = iBlockSize > minBlockSize ? iBlockSize : minBlockSize;
	const size_t allocSize = blockSize * iNumBlocks;

	assert(allocSize / iNumBlocks == blockSize); // check overflow

	mData = static_cast<BlockDataPtr>( MM_MALLOC( allocSize ) );
	mFirstAvailableBlock = 0;
	mNumAvailableBlocks = iNumBlocks;

	BlockDataPtr blockPtr = mData;
	for(size_t i = 0; i != iNumBlocks; blockPtr += blockSize)
	{
		*blockPtr = ++i;
	}
}



void* Chunk2::Allocate(const size_t iBlockSize)
{
	assert(!IsFull());

	assert((mFirstAvailableBlock * iBlockSize) / iBlockSize == mFirstAvailableBlock);	// check overflow

	BlockDataPtr allocatedMem = mData + (mFirstAvailableBlock * iBlockSize);
	mFirstAvailableBlock = *allocatedMem; // updates mFirstAvailableBlock to point to the next block

	--mNumAvailableBlocks;

	return allocatedMem;
}



void Chunk2::Deallocate(void* iPtr, const size_t iBlockSize)
{
	assert(iPtr >= mData); // Lower bound check

	BlockDataPtr toRelease = static_cast<BlockDataPtr>(iPtr);

	assert((toRelease - mData) % iBlockSize == 0); // Alignment check

	// Deallocating blocks means set their headers to the next available block
	*reinterpret_cast<BlockCounterPtr>(toRelease) = mFirstAvailableBlock;
	mFirstAvailableBlock = static_cast<BlockCounter>((toRelease - mData) / iBlockSize);

	assert(mFirstAvailableBlock == (toRelease - mData) / iBlockSize); // Truncation check

	++mNumAvailableBlocks;
}



void Chunk2::Release(void)
{
	assert(nullptr != mData);

	MM_FREE( mData );
}

};